package com.bjy.qa.service.performancetest.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bjy.qa.dao.functionaltest.AgentDao;
import com.bjy.qa.dao.manage.ProjectDao;
import com.bjy.qa.dao.performancetest.*;
import com.bjy.qa.dao.project.RobotDao;
import com.bjy.qa.entity.MyPage;
import com.bjy.qa.entity.ResponsePagingData;
import com.bjy.qa.entity.functionaltest.Agent;
import com.bjy.qa.entity.manage.Project;
import com.bjy.qa.entity.performancetest.*;
import com.bjy.qa.entity.project.Robot;
import com.bjy.qa.enumtype.PerfLogType;
import com.bjy.qa.enumtype.RunStatus;
import com.bjy.qa.exception.MyException;
import com.bjy.qa.service.performancetest.IPerfTestResultService;
import com.bjy.qa.util.RobotMsgTool;
import com.bjy.qa.util.SpringTool;
import com.bjy.qa.util.TimeUtil;
import com.csvreader.CsvReader;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.bjy.qa.util.NumberUtils.decimalFormat;

@Service
public class PerfTestResultService extends ServiceImpl<PerfTestResultDao, PerfTestResult> implements IPerfTestResultService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    PerfTestSuiteDao perfTestSuiteDao;
    @Resource
    PerfTestResultDao perfTestResultDao;
    @Resource
    PerfTestResultTestScriptDao perfTestResultTestScriptDao;
    @Resource
    PerfTestSuiteTestScriptDao perfTestSuiteTestScriptDao;
    @Resource
    PerfTestResultLogDao perfTestResultLogDao;
    @Resource
    PerfTestResultReportDao perfTestResultReportDao;
    @Resource
    TestScriptDao testScriptDao;
    @Resource
    AgentDao agentDao;
    @Resource
    RobotDao robotDao;
    @Resource
    private RobotMsgTool robotMsgTool;
    @Resource
    ProjectDao projectDao;

    @Override
    public ResponsePagingData list(MyPage<PerfTestResult> myPage) {
        Page<PerfTestResult> page = new Page<>(myPage.getPageNum(), myPage.getPageSize()); // 构造待查询 page 对象
        page.setOrders(OrderItem.descs("id")); // 按照id倒序排

        PerfTestResult perfTestResult = myPage.getQuery();
        QueryWrapper<PerfTestResult> queryWrapper = new QueryWrapper<>();

        if (perfTestResult != null) {
            if (StringUtils.isNotBlank(perfTestResult.getProjectId().toString())) {
                queryWrapper.eq("project_id", perfTestResult.getProjectId());
            }
            if (perfTestResult.getCaseType().getValue() != 0) {
                queryWrapper.eq("case_type", perfTestResult.getCaseType());
            }
            if (perfTestResult.getSuiteId() != null) {
                queryWrapper.eq("suite_id", perfTestResult.getSuiteId());
            }
        }
        page = perfTestResultDao.selectPage(page, queryWrapper);
        myPage.setTotalRecords(page.getTotal());
        return new ResponsePagingData(myPage, page.getRecords());
    }

    @Override
    public PerfTestResult getPerfTestResultInfoById(Long resultId) {
        PerfTestResult perfTestResult = perfTestResultDao.selectById(resultId);
        if (perfTestResult == null) {
            throw new MyException("测试报告不存在，或已清除。 resultId:" + resultId);
        }

        List<PerfTestResultTestScript> testResultCaseList = perfTestSuiteTestScriptDao.selecByResultId(resultId);

        for (int i = 0; i < testResultCaseList.size(); i++) {
            TestScript testScript = testScriptDao.findById(testResultCaseList.get(i).getTestScriptId()); // 查找测试脚本（包含已删除测试脚本），比如在测试报告中也应该能显示出来
            testResultCaseList.get(i).setTestScriptName(testScript.getName()); // 设置测试脚本名称

            // 设置 agent name
            Agent agent = agentDao.selectById(testResultCaseList.get(i).getAgent());
            testResultCaseList.get(i).setAgentName(agent.getName());
        }
        perfTestResult.setPerfTestResultTestScriptList(testResultCaseList);

        return perfTestResult;
    }

    @Override
    public List<PerfTestResultLog> getCaseResultDetails(Long resultId, Long testScriptId) {
        return perfTestResultLogDao.selecByCaseIdAndResultId(resultId, testScriptId);
    }

    @Override
    public ReportDTO getReport(Long resultId, Long testScriptId) {
        List<PerfTestResultReport> perfTestResultReportList = perfTestResultReportDao.getReport(resultId, testScriptId);

        ReportDTO reportDTO = new ReportDTO();
        reportDTO.setResultId(resultId);
        reportDTO.setTestScriptId(testScriptId);
        for (PerfTestResultReport perfTestResultReport : perfTestResultReportList) {
            if (perfTestResultReport.getLogType() == PerfLogType.CSV_REQUESTS) {
                reportDTO.setRequests(JSONObject.parseObject(perfTestResultReport.getData()));
            } else if (perfTestResultReport.getLogType() == PerfLogType.CSV_EXCEPTIONS) {
                reportDTO.setExceptions(JSONObject.parseObject(perfTestResultReport.getData()));
            } else if (perfTestResultReport.getLogType() == PerfLogType.CSV_FAILURES) {
                reportDTO.setFailures(JSONObject.parseObject(perfTestResultReport.getData()));
            } else if (perfTestResultReport.getLogType() == PerfLogType.DIFF_RESULT) {
                reportDTO.setDiffResult(JSONObject.parseObject(perfTestResultReport.getData()));
            }
        }
        return reportDTO;
    }

    @Override
    public LatestDataDTO getLatestData(Long resultId, Long testScriptId) {
        PerfTestResultReport perfTestResultReport = perfTestResultReportDao.getByPerfLogType(PerfLogType.LATEST_DATA, resultId, testScriptId);
        if (perfTestResultReport == null) { // 为空，没有缓存
            return this.getLatestDataFromTestResultLog(resultId, testScriptId); // 从 pt_test_result_log 查数据
        } else { // 有缓存，用直接转对象返回
            return JSONObject.parseObject(perfTestResultReport.getData(), LatestDataDTO.class);
        }
    }

    /**
     * 直接从 pt_test_result_log 查询 性能测试实时图表页签 - 表格中显示的数据（未走缓存）
     * @param resultId 测试结果 ID
     * @param testScriptId 测试脚本 ID
     * @return
     */
    private LatestDataDTO getLatestDataFromTestResultLog(Long resultId, Long testScriptId) {
        LatestDataDTO latestDataDTO = new LatestDataDTO();
        latestDataDTO.setResultId(resultId);
        latestDataDTO.setTestScriptId(testScriptId);

        PerfTestResultLog perfTestResultLog = perfTestResultLogDao.getLatestData(PerfLogType.LOG_REQUESTS, resultId, testScriptId);
        if (perfTestResultLog != null) {
            latestDataDTO.setRequests(JSONObject.parseObject(perfTestResultLog.getLog()));
        }

        perfTestResultLog = perfTestResultLogDao.getLatestData(PerfLogType.LOG_EXCEPTIONS, resultId, testScriptId);
        if (perfTestResultLog != null) {
            latestDataDTO.setExceptions(JSONObject.parseObject(perfTestResultLog.getLog()));
        }

        perfTestResultLog = perfTestResultLogDao.getLatestData(PerfLogType.LOG_TASKS, resultId, testScriptId);
        if (perfTestResultLog != null) {
            latestDataDTO.setTasks(JSONObject.parseObject(perfTestResultLog.getLog()));
        }
        return latestDataDTO;
    }

    public ChartDTO getChart(Long resultId, Long testScriptId) {
        PerfTestResultReport perfTestResultReport = perfTestResultReportDao.getByPerfLogType(PerfLogType.CHART, resultId, testScriptId);
        if (perfTestResultReport == null) { // 为空，没有缓存
            return this.getChartFromTestResultLog(resultId, testScriptId); // 从 pt_test_result_log 查数据
        } else { // 有缓存，用直接转对象返回
            return JSONObject.parseObject(perfTestResultReport.getData(), ChartDTO.class);
        }
    }

    /**
     * 直接从 pt_test_result_log 查询 性能测试实时图表页签 - Chart 中显示的数据（未走缓存）
     * @param resultId 测试结果 ID
     * @param testScriptId 测试脚本 ID
     * @return
     */
    private ChartDTO getChartFromTestResultLog(Long resultId, Long testScriptId) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));

        ChartDTO chartDTO = new ChartDTO();
        chartDTO.setResultId(resultId);
        chartDTO.setTestScriptId(testScriptId);

        List<String> timeList = new LinkedList<>(); // 时间（x轴）
        List<Integer> userCountList = new LinkedList<>(); // 用户数
        List<Float> responseTimePercentile50List = new LinkedList<>(); // 50% 响应时间（中位数）
        List<Float> responseTimePercentile95List = new LinkedList<>(); // 95% 响应时间
        List<Float> currentRequestsPerSecList = new LinkedList<>(); // 每秒请求数
        List<Float> currentFailPerSecList = new LinkedList<>(); // 每秒失败数

        // 遍历 性能测试结果日志表（pt_test_result_log）
        List<PerfTestResultLog> perfTestResultLogList = perfTestResultLogDao.getByPerfLogType(PerfLogType.LOG_REQUESTS, resultId, testScriptId);
        for (PerfTestResultLog perfTestResultLog : perfTestResultLogList) {
            JSONObject logJsonObject = JSONObject.parseObject(perfTestResultLog.getLog());

            timeList.add(sdf.format(perfTestResultLog.getCreatedAt())); // 时间（x轴）
            userCountList.add(logJsonObject.getIntValue("user_count")); // 用户数
            responseTimePercentile50List.add(logJsonObject.getFloatValue("current_response_time_percentile_50")); // 50% 响应时间（中位数）
            responseTimePercentile95List.add(logJsonObject.getFloatValue("current_response_time_percentile_95")); // 95% 响应时间

            boolean existAggregated = false;
            JSONArray statsJsonArray = logJsonObject.getJSONArray("stats");
            for (int i = 0; i < statsJsonArray.size(); i++) {
                JSONObject statsJsonObject = statsJsonArray.getJSONObject(i);
                if ("Aggregated".equals(statsJsonObject.getString("name"))) {
                    existAggregated = true;
                    currentRequestsPerSecList.add(statsJsonObject.getFloatValue("current_rps")); // 每秒请求数
                    currentFailPerSecList.add(statsJsonObject.getFloatValue("current_fail_per_sec")); // 每秒失败数
                }
            }
            if (!existAggregated) { // 如果没有找到 Aggregated 补 0
                currentRequestsPerSecList.add(0f); // 每秒请求数
                currentFailPerSecList.add(0f); // 每秒失败数
            }
        }

        chartDTO.setTime(timeList); // 时间（x轴）
        chartDTO.setUserCount(userCountList); // 用户数
        chartDTO.setResponseTimePercentile50(responseTimePercentile50List); // 50% 响应时间（中位数）
        chartDTO.setResponseTimePercentile95(responseTimePercentile95List); // 95% 响应时间
        chartDTO.setCurrentRequestsPerSec(currentRequestsPerSecList); // 每秒请求数
        chartDTO.setCurrentFailPerSec(currentFailPerSecList); // 每秒失败数
        return chartDTO;
    }

    @Async
    @Override
    public void asyncCountChartData(Long resultId, Long testScriptId) {
        // 清除之前结果
        QueryWrapper<PerfTestResultReport> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("result_id", resultId);
        queryWrapper.eq("test_script_id", testScriptId).and(wrapper ->
                wrapper.eq("log_type", PerfLogType.CHART)
                        .or().eq("log_type", PerfLogType.LATEST_DATA)
        );
        perfTestResultReportDao.delete(queryWrapper);

        // 从 pt_test_result_log 查 性能测试实时图表页签 - 表格中显示的数据，并保存到 pt_test_result_report 表中
        LatestDataDTO latestDataDTO = this.getLatestDataFromTestResultLog(resultId, testScriptId);
        PerfTestResultReport perfTestResultReport = new PerfTestResultReport();
        perfTestResultReport.setResultId(resultId);
        perfTestResultReport.setTestScriptId(testScriptId);
        perfTestResultReport.setLogType(PerfLogType.LATEST_DATA);
        perfTestResultReport.setDesc(PerfLogType.LATEST_DATA.getName());
        perfTestResultReport.setData(JSONObject.toJSONString(latestDataDTO));
        perfTestResultReportDao.insert(perfTestResultReport);

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }

        // 从 pt_test_result_log 查 性能测试实时图表页签 - Chart 中显示的数据，并保存到 pt_test_result_report 表中
        ChartDTO chartDTO = this.getChartFromTestResultLog(resultId, testScriptId);
        perfTestResultReport = new PerfTestResultReport();
        perfTestResultReport.setResultId(resultId);
        perfTestResultReport.setTestScriptId(testScriptId);
        perfTestResultReport.setLogType(PerfLogType.CHART);
        perfTestResultReport.setDesc(PerfLogType.CHART.getName());
        perfTestResultReport.setData(JSONObject.toJSONString(chartDTO));
        perfTestResultReportDao.insert(perfTestResultReport);
    }

    @Override
    public String log(PerfTestResultLog perfTestResultLog) {
        Long resultId = perfTestResultLog.getResultId(); // 测试结果 id
        Long testScriptId = perfTestResultLog.getTestScriptId(); // 测试脚本 id

        perfTestResultLogDao.insert(perfTestResultLog); // 保存 性能测试结果日志

        // 保存 性能测试结果报告
        if (perfTestResultLog.getLogType() == PerfLogType.CSV_REQUESTS || perfTestResultLog.getLogType() == PerfLogType.CSV_EXCEPTIONS || perfTestResultLog.getLogType() == PerfLogType.CSV_FAILURES) {
            insertPerfTestResultReport(perfTestResultLog.getLogType(), perfTestResultLog.getResultId(), perfTestResultLog.getTestScriptId(), perfTestResultLog.getLog());
        }

        // 如果 msg 为 status，则更新 性能测试结果 对应 性能测试脚本 表（pt_test_result_test_script）
        if ("status".equalsIgnoreCase(perfTestResultLog.getMsg())) {
            PerfTestResultTestScript perfTestResultTestScript = perfTestResultTestScriptDao.getTestResultTestScript(resultId, testScriptId); // 根据 测试结果 ID 和 测试脚本 ID 查询 测试结果对应测试脚本
            if (perfTestResultTestScript == null) {
                throw new MyException("性能测试结果处理失败，上报的 resultId: " + resultId + " 中没有对应的 testScriptId: " + testScriptId);
            }

            RunStatus runStatus = perfTestResultLog.getStatus(); // 上报的 执行状态

            // 上报执行状态为 通过 时，查找性能基线并与基线比对，比基线 慢 或 资源消耗多 设置为 失败
            if (runStatus == RunStatus.PASSED) {
                PerfTestResult testResultSuite = perfTestResultDao.selectById(resultId);
                if (testResultSuite.getSuiteId() > 0) { // SuiteId 小于 0 为 debug 下发的套件，所以非 debug 的套件才   发送机器人消息
                    List<PerfTestResult> perfTestResultBaseLineList = perfTestResultDao.selectPerfTestResultBaseLine(testResultSuite.getSuiteId());
                    if (perfTestResultBaseLineList.size() > 0) { // 如果此 suiteId 设置了基线，则与基线比对
                        runStatus = diffReport(perfTestResultBaseLineList.get(0).getId(), testScriptId, resultId, testScriptId);
                    }
                }
            }

            // 如果上报的 测试脚本状态 大于 库里的 测试脚本状态 更新
            if (perfTestResultTestScript.getStatus().getValue() < runStatus.getValue()) {
                perfTestResultTestScript.setStatus(runStatus.getValue());
                perfTestResultTestScript.setUpdatedAt(perfTestResultLog.getCreatedAt());
                perfTestResultTestScript.setCreatedAt(null);
                perfTestResultTestScriptDao.updateById(perfTestResultTestScript);

                // 异步计算性能测试实时图表页签上的数据（LatestData、Chart）
                if (runStatus.getValue() >= RunStatus.PASSED.getValue()) {
                    SpringTool.getBean(IPerfTestResultService.class).asyncCountChartData(resultId, testScriptId);
                }
            }

            // 上报状态为 RUNNING 时，只需修改 性能测试结果 对应 性能测试脚本 的状态，不需要再处理其它
            if (runStatus == RunStatus.RUNNING) {
                return "测试结果处理成功";
            }

            // 套件运行完毕后更新 PerfTestResult 表
            if (perfTestResultTestScriptDao.countRunning(resultId) == 0) {
                PerfTestResult testResultSuite = perfTestResultDao.selectById(resultId);
                testResultSuite.setEndAt(perfTestResultLog.getCreatedAt());
                testResultSuite.setTotalTime(TimeUtil.getTimeDiff(testResultSuite.getCreatedAt(), perfTestResultLog.getCreatedAt()));
                int testResultStatus = perfTestResultTestScriptDao.getTestResultStatus(resultId);
                if (testResultStatus == RunStatus.ERROR.getValue()) { // 当测试套件运行状态为 ERROR 时改为 FAILED
                    testResultStatus = RunStatus.FAILED.getValue();
                }
                testResultSuite.setStatus(testResultStatus);
                testResultSuite.setUpdatedAt(perfTestResultLog.getCreatedAt());
                testResultSuite.setCreatedAt(null);
                perfTestResultDao.updateById(testResultSuite);

                // 套件运行结束后，发送机器人消息
                if (testResultSuite.getSuiteId() > 0) { // SuiteId 小于 0 为 debug 下发的套件，所以非 debug 的套件才发送机器人消息
                    PerfTestSuite perfTestSuite = perfTestSuiteDao.selectById(testResultSuite.getSuiteId());
                    Robot robot = robotDao.selectById(perfTestSuite.getRobotId());
                    if (perfTestSuite != null && robot != null && robot.getRobotToken().length() > 0) {
                        Project project = projectDao.selectById(perfTestSuite.getProjectId());
                        String reportUri = String.format("/#/performance_test/perf_result_detail?companyId=%d&projectId=%d&resultId=%d", project.getCompanyId(), project.getId(), resultId);
                        int pass = perfTestResultTestScriptDao.countPassed(resultId);
                        int warn = perfTestResultTestScriptDao.countWarn(resultId);
                        int skip = perfTestResultTestScriptDao.countSkipped(resultId);
                        int fail = perfTestResultTestScriptDao.countFailed(resultId);
                        logger.info("性能测试套件完成，发送测试报告： resultId: {}, suiteName: {}, pass: {}, warn: {}, skip: {}, fail: {}", resultId, testResultSuite.getSuiteName(), pass, warn, skip, fail);
                        robotMsgTool.sendResultFinishReport(robot.getRobotToken(), robot.getRobotSecret(), "性能报告", testResultSuite.getSuiteName(), pass, warn, skip, fail, reportUri, 1);
                    }
                }
            }
        }

        return "测试结果处理成功";
    }

    /**
     * 与 基线 Report 对比（比对内容：响应时间、资源消耗 等）
     * @param baseLineResultId 基线测试结果 ID
     * @param baseLineTestScriptId 基线测试脚本 ID
     * @param resultId 测试结果 ID
     * @param testScriptId 测试脚本 ID
     * @return FAILED：比基线 慢 或 资源消耗多，PASSED：比基线 快 或 资源消耗少
     */
    private RunStatus diffReport(Long baseLineResultId, Long baseLineTestScriptId, Long resultId, Long testScriptId) {
        double thresholdValue = 0.1; // 阈值，默认 10%

        JSONObject diffResult = new JSONObject(); // 比对结果

        /*
         * 比对 CSV_REQUESTS 数据
         */
        PerfTestResultReport baseLineCsvRequests = perfTestResultReportDao.getByPerfLogType(PerfLogType.CSV_REQUESTS, baseLineResultId, baseLineTestScriptId);
        JSONObject baseLineCsvRequestsJsonObject = JSONObject.parseObject(baseLineCsvRequests.getData());
        JSONArray baseLineCsvRequestsBodyList = baseLineCsvRequestsJsonObject.getJSONArray("body"); // 基线

        PerfTestResultReport csvRequests = perfTestResultReportDao.getByPerfLogType(PerfLogType.CSV_REQUESTS, resultId, testScriptId);
        JSONObject csvRequestsJsonObject = JSONObject.parseObject(csvRequests.getData());
        JSONArray csvRequestsBodyList = csvRequestsJsonObject.getJSONArray("body"); // 新结果

        JSONArray csvRequestsResult = new JSONArray(); // 请求结果
        // 遍历基线请求数据，响应时间慢
        for (int i = 0; i < csvRequestsBodyList.size(); i++) {
            JSONObject newCsvRequest = csvRequestsBodyList.getJSONObject(i);
            for (int j = 0; j < baseLineCsvRequestsBodyList.size(); j++) {
                JSONObject baseLineCsvRequest = baseLineCsvRequestsBodyList.getJSONObject(j);
                if (newCsvRequest.getString("name").equals(baseLineCsvRequest.getString("name"))) {
                    String requestsPath;
                    if ("Aggregated".equals(newCsvRequest.getString("name"))) {
                        requestsPath = "汇总";
                    } else {
                        requestsPath = "请求：" + newCsvRequest.getString("name");
                    }

                    // 平均响应时间（Average Response Time）
                    double timeDiffRatio = (newCsvRequest.getDoubleValue("averageResponseTime") - baseLineCsvRequest.getDoubleValue("averageResponseTime")) / baseLineCsvRequest.getDoubleValue("averageResponseTime");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，平均响应时间（Average Response Time）变慢 （基线：" + decimalFormat("#", baseLineCsvRequest.getDoubleValue("averageResponseTime")) + "ms，当前：" + decimalFormat("#", newCsvRequest.getDoubleValue("averageResponseTime")) + "ms，慢了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }

                    // 95% 响应时间
                    timeDiffRatio = (newCsvRequest.getDoubleValue("percent95") - baseLineCsvRequest.getDoubleValue("percent95")) / baseLineCsvRequest.getDoubleValue("percent95");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，95% 响应时间 变慢 （基线：" + decimalFormat("#", baseLineCsvRequest.getDoubleValue("percent95")) + "ms，当前：" + decimalFormat("#", newCsvRequest.getDoubleValue("percent95")) + "ms，慢了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }

                    // 90% 响应时间
                    timeDiffRatio = (newCsvRequest.getDoubleValue("percent90") - baseLineCsvRequest.getDoubleValue("percent90")) / baseLineCsvRequest.getDoubleValue("percent90");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，90% 响应时间 变慢 （基线：" + decimalFormat("#", baseLineCsvRequest.getDoubleValue("percent90")) + "ms，当前：" + decimalFormat("#", newCsvRequest.getDoubleValue("percent90")) + "ms，慢了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }

                    // 80% 响应时间
                    timeDiffRatio = (newCsvRequest.getDoubleValue("percent80") - baseLineCsvRequest.getDoubleValue("percent80")) / baseLineCsvRequest.getDoubleValue("percent80");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，80% 响应时间 变慢 （基线：" + decimalFormat("#", baseLineCsvRequest.getDoubleValue("percent80")) + "ms，当前：" + decimalFormat("#", newCsvRequest.getDoubleValue("percent80")) + "ms，慢了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }

                    // 中位数（Median Response Time）
                    timeDiffRatio = (newCsvRequest.getDoubleValue("medianResponseTime") - baseLineCsvRequest.getDoubleValue("medianResponseTime")) / baseLineCsvRequest.getDoubleValue("medianResponseTime");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，中位数（Median Response Time）变慢 （基线：" + decimalFormat("#", baseLineCsvRequest.getDoubleValue("medianResponseTime")) + "ms，当前：" + decimalFormat("#", newCsvRequest.getDoubleValue("medianResponseTime")) + "ms，慢了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }

                    // 每秒处理的请求个数（RPS / QPS）
                    timeDiffRatio = (baseLineCsvRequest.getDoubleValue("requestsPerSec") - newCsvRequest.getDoubleValue("requestsPerSec")) / baseLineCsvRequest.getDoubleValue("requestsPerSec");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，每秒请求数（RPS / QPS） 变少 （基线：" + decimalFormat("#.##", baseLineCsvRequest.getDoubleValue("requestsPerSec")) + "次，当前：" + decimalFormat("#.##", newCsvRequest.getDoubleValue("requestsPerSec")) + "次，少了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }

                    // 每秒失败的请求个数
                    timeDiffRatio = (newCsvRequest.getDoubleValue("failuresPerSec") - baseLineCsvRequest.getDoubleValue("failuresPerSec")) / baseLineCsvRequest.getDoubleValue("failuresPerSec");
                    if (timeDiffRatio > thresholdValue) {
                        csvRequestsResult.add(requestsPath + "，每秒失败数 变多 （基线：" + decimalFormat("#.##", baseLineCsvRequest.getDoubleValue("failuresPerSec")) + "次，当前：" + decimalFormat("#.##", newCsvRequest.getDoubleValue("failuresPerSec")) + "次，多了：" + decimalFormat("#", timeDiffRatio * 100) + "%）");
                    }
                    break;
                }
            }
        }
        diffResult.put("csvRequests", csvRequestsResult);

        /*
         * 比对 CSV_FAILURES 数据
         */
        PerfTestResultReport baseLineCsvFailures = perfTestResultReportDao.getByPerfLogType(PerfLogType.CSV_FAILURES, baseLineResultId, baseLineTestScriptId);
        JSONObject baseLineCsvFailuresJsonObject = JSONObject.parseObject(baseLineCsvFailures.getData());
        JSONArray baseLineCsvFailuresBodyList = baseLineCsvFailuresJsonObject.getJSONArray("body"); // 基线

        PerfTestResultReport csvFailures = perfTestResultReportDao.getByPerfLogType(PerfLogType.CSV_FAILURES, resultId, testScriptId);
        JSONObject csvFailuresJsonObject = JSONObject.parseObject(csvFailures.getData());
        JSONArray csvFailuresBodyList = csvFailuresJsonObject.getJSONArray("body"); // 新结果

        JSONArray csvFailuresResult = new JSONArray(); // 失败结果
        if (csvFailuresBodyList.size() > 0 && baseLineCsvFailuresBodyList.size() == 0) { // 有差异（新测试结果有 失败数据，但基线失败数据为空）
            csvFailuresResult.add("基线中没失败，但本次测试出现失败");
        } else {
            // 遍历基线失败数据，找出新增的失败 或 失败失败次数超过基线次数
            for (int i = 0; i < csvFailuresBodyList.size(); i++) {
                JSONObject newCsvFailure = csvFailuresBodyList.getJSONObject(i);
                boolean exist = false;
                for (int j = 0; j < baseLineCsvFailuresBodyList.size(); j++) {
                    JSONObject baseLineCsvFailure = baseLineCsvFailuresBodyList.getJSONObject(j);
                    if (newCsvFailure.getString("name").equals(baseLineCsvFailure.getString("name"))) {
                        if (newCsvFailure.getIntValue("occurrences") > baseLineCsvFailure.getIntValue("occurrences")) {
                            csvFailuresResult.add("失败：" + newCsvFailure.getString("name") + "，出现的次数超过基线中的次数。基线出现次数：" + baseLineCsvFailure.getIntValue("occurrences") + "， 本次出现次数：" + newCsvFailure.getIntValue("occurrences"));
                        }
                        exist = true;
                        break;
                    }
                }
                if (!exist) {
                    csvFailuresResult.add("新增失败：" + newCsvFailure.getString("name"));
                }
            }
        }
        diffResult.put("csvFailures", csvFailuresResult);

        /*
         * 比对 CSV_EXCEPTIONS 数据
         */
        PerfTestResultReport baseLineCsvExceptions = perfTestResultReportDao.getByPerfLogType(PerfLogType.CSV_EXCEPTIONS, baseLineResultId, baseLineTestScriptId);
        JSONObject baseLineCsvExceptionsJsonObject = JSONObject.parseObject(baseLineCsvExceptions.getData());
        JSONArray baseLineCsvExceptionsBodyList = baseLineCsvExceptionsJsonObject.getJSONArray("body"); // 基线

        PerfTestResultReport csvExceptions = perfTestResultReportDao.getByPerfLogType(PerfLogType.CSV_EXCEPTIONS, resultId, testScriptId);
        JSONObject csvExceptionsJsonObject = JSONObject.parseObject(csvExceptions.getData());
        JSONArray csvExceptionsBodyList = csvExceptionsJsonObject.getJSONArray("body"); // 新结果

        JSONArray csvExceptionsResult = new JSONArray(); // 异常结果
        if (csvExceptionsBodyList.size() > 0 && baseLineCsvExceptionsBodyList.size() == 0) { // 有差异（新测试结果有 异常数据，但基线异常数据为空）
            csvExceptionsResult.add("基线中没异常，但本次测试出现异常");
        } else {
            // 遍历基线异常数据，找出新增的异常 或 异常次数超过基线次数
            for (int i = 0; i < csvExceptionsBodyList.size(); i++) {
                JSONObject newCsvException = csvExceptionsBodyList.getJSONObject(i);
                boolean exist = false;
                for (int j = 0; j < baseLineCsvExceptionsBodyList.size(); j++) {
                    JSONObject baseLineCsvException = baseLineCsvExceptionsBodyList.getJSONObject(j);
                    if (newCsvException.getString("message").equals(baseLineCsvException.getString("message"))) {
                        if (newCsvException.getIntValue("count") > baseLineCsvException.getIntValue("count")) {
                            csvExceptionsResult.add("异常：" + newCsvException.getString("message") + "，出现的次数超过基线中的次数。基线出现次数：" + baseLineCsvException.getIntValue("count") + "， 本次出现次数：" + newCsvException.getIntValue("count"));
                        }
                        exist = true;
                        break;
                    }
                }
                if (!exist) {
                    csvExceptionsResult.add("新增异常：" + newCsvException.getString("message"));
                }
            }
        }
        diffResult.put("csvExceptions", csvExceptionsResult);

        // 保存与 基线 Report 对比结果
        PerfTestResultReport perfTestResultReport = new PerfTestResultReport();
        perfTestResultReport.setResultId(resultId);
        perfTestResultReport.setTestScriptId(testScriptId);
        perfTestResultReport.setType(PerfLogType.DIFF_RESULT.getValue());
        perfTestResultReport.setDesc(PerfLogType.DIFF_RESULT.getName());
        perfTestResultReport.setData(JSONObject.toJSONString(diffResult));
        perfTestResultReportDao.insert(perfTestResultReport);

        if (csvRequestsResult.size() == 0 && csvFailuresResult.size() == 0 && csvExceptionsResult.size() == 0) { // 如果都是 0，表示比基线 快 或 资源消耗少，不改动 PASSED 状态，直接返回 PASSED
            return RunStatus.PASSED;
        } else { // 否则，表示比基线 慢 或 资源消耗多，返回 FAILED
            return RunStatus.FAILED;
        }
    }

    /**
     * 保存 性能测试结果报告
     * @param type 类型 requests、exceptions、failures
     * @param resultId 测试结果 id
     * @param testScriptId 测试脚本 id
     * @param dataStr 上报的 csv 数据内容
     * @return
     */
    private boolean insertPerfTestResultReport(PerfLogType type, Long resultId, Long testScriptId, String dataStr) {
        PerfTestResultReport perfTestResultReport = new PerfTestResultReport();
        perfTestResultReport.setResultId(resultId);
        perfTestResultReport.setTestScriptId(testScriptId);
        perfTestResultReport.setType(type.getValue());
        perfTestResultReport.setDesc(type.getName());

        if (type == PerfLogType.CSV_REQUESTS) {
            perfTestResultReport.setData(parseRequestsCsv(dataStr));
        } else if (type == PerfLogType.CSV_EXCEPTIONS) {
            perfTestResultReport.setData(parseExceptionsCsv(dataStr));
        } else if (type == PerfLogType.CSV_FAILURES) {
            perfTestResultReport.setData(parseFailuresCsv(dataStr));
        }

        perfTestResultReportDao.insert(perfTestResultReport);
        return true;
    }

    /**
     * 解析 /stats/requests/csv 数据
     * @param csvString 上报的 csv 数据内容
     * @return
     */
    private String parseRequestsCsv(String csvString) {
        InputStream is = new ByteArrayInputStream(csvString.getBytes());
        CsvReader reader = null;
        JSONObject jsonObject = new JSONObject();

        try {
            // 读取 csv 文件
            reader = new CsvReader(is, ',', Charset.forName("GBK"));
            reader.readHeaders();

            // 设置 header
            Map<String, String> headers = new HashMap<>();
            headers.put("type", "Type");
            headers.put("name", "Name");
            headers.put("requestCount", "Request Count");
            headers.put("failureCount", "Failure Count");
            headers.put("medianResponseTime", "Median Response Time");
            headers.put("averageResponseTime", "Average Response Time");
            headers.put("minResponseTime", "Min Response Time");
            headers.put("maxResponseTime", "Max Response Time");
            headers.put("averageContentSize", "Average Content Size");
            headers.put("requestsPerSec", "Requests/s");
            headers.put("failuresPerSec", "Failures/s");
            headers.put("percent50", "50%");
            headers.put("percent66", "66%");
            headers.put("percent75", "75%");
            headers.put("percent80", "80%");
            headers.put("percent90", "90%");
            headers.put("percent95", "95%");
            headers.put("percent98", "98%");
            headers.put("percent99", "99%");
            headers.put("percent99Point9", "99.9%");
            headers.put("percent99Point99", "99.99%");
            headers.put("percent100", "100%");
            jsonObject.put("headers", headers);

            // 设置 body
            JSONArray body = new JSONArray();
            while (reader.readRecord()) {
                JSONObject row =  new JSONObject();
                row.put("type", reader.get("Type"));
                row.put("name", reader.get("Name"));
                row.put("requestCount", reader.get("Request Count"));
                row.put("failureCount", reader.get("Failure Count"));
                row.put("medianResponseTime", reader.get("Median Response Time"));
                row.put("averageResponseTime", reader.get("Average Response Time"));
                row.put("minResponseTime", reader.get("Min Response Time"));
                row.put("maxResponseTime", reader.get("Max Response Time"));
                row.put("averageContentSize", reader.get("Average Content Size"));
                row.put("requestsPerSec", reader.get("Requests/s"));
                row.put("failuresPerSec", reader.get("Failures/s"));
                row.put("percent50", reader.get("50%"));
                row.put("percent66", reader.get("66%"));
                row.put("percent75", reader.get("75%"));
                row.put("percent80", reader.get("80%"));
                row.put("percent90", reader.get("90%"));
                row.put("percent95", reader.get("95%"));
                row.put("percent98", reader.get("98%"));
                row.put("percent99", reader.get("99%"));
                row.put("percent99Point9", reader.get("99.9%"));
                row.put("percent99Point99", reader.get("99.99%"));
                row.put("percent100", reader.get("100%"));
                body.add(row);
            }
            jsonObject.put("body", body);
        } catch (Exception e) {
            logger.error("解析 /stats/requests/csv 数据错误：", e);
            e.printStackTrace();
        } finally {
            if (null != reader) {
                reader.close();
            }
        }
        return JSONObject.toJSONString(jsonObject);
    }

    /**
     * 解析 /exceptions/csv 数据
     * @param csvString 上报的 csv 数据内容
     * @return
     */
    private String parseExceptionsCsv(String csvString) {
        InputStream is = new ByteArrayInputStream(csvString.getBytes());
        CsvReader reader = null;
        JSONObject jsonObject = new JSONObject();

        // 设置 header
        Map<String, String> headers = new HashMap<>();
        headers.put("count", "Count");
        headers.put("message", "Message");
        headers.put("traceback", "Traceback");
        headers.put("nodes", "Nodes");
        jsonObject.put("headers", headers);

        try {
            // 读取 csv 文件
            reader = new CsvReader(is, ',', Charset.forName("GBK"));
            reader.readHeaders();

            // 设置 body
            JSONArray body = new JSONArray();
            while (reader.readRecord()) {
                JSONObject row =  new JSONObject();
                row.put("count", reader.get("Count"));
                row.put("message", reader.get("Message"));
                row.put("traceback", reader.get("Traceback"));
                row.put("nodes", reader.get("Nodes"));
                body.add(row);
            }
            jsonObject.put("body", body);
        } catch (Exception e) {
            logger.error("解析 /exceptions/csv 数据错误：", e);
            e.printStackTrace();
        } finally {
            if (null != reader) {
                reader.close();
            }
        }
        return JSONArray.toJSONString(jsonObject);
    }

    /**
     * 解析 /stats/failures/csv 数据
     * @param csvString 上报的 csv 数据内容
     * @return
     */
    private String parseFailuresCsv(String csvString) {
        InputStream is = new ByteArrayInputStream(csvString.getBytes());
        CsvReader reader = null;
        JSONObject jsonObject = new JSONObject();

        // 设置 header
        Map<String, String> headers = new HashMap<>();
        headers.put("method", "Method");
        headers.put("name", "name");
        headers.put("error", "Error");
        headers.put("occurrences", "Occurrences");
        jsonObject.put("headers", headers);

        try {
            // 读取 csv 文件
            reader = new CsvReader(is, ',', Charset.forName("utf-8"));
            reader.readHeaders();

            // 设置 body
            JSONArray body = new JSONArray();
            while (reader.readRecord()) {
                JSONObject row =  new JSONObject();
                row.put("method", reader.get("Method"));
                row.put("name", reader.get("Name"));
                row.put("error", reader.get("Error"));
                row.put("occurrences", reader.get("Occurrences"));
                body.add(row);
            }
            jsonObject.put("body", body);
        } catch (Exception e) {
            logger.error("解析 /stats/failures/csv 数据错误：", e);
            e.printStackTrace();
        } finally {
            if (null != reader) {
                reader.close();
            }
        }
        return JSONArray.toJSONString(jsonObject);
    }
}
